use hirun::runtime::{self, block_on, Builder};
use std::fmt::Debug;
use std::fs;
use std::path::Path;
use std::time::Instant;

fn main() {
    Builder::new().build().unwrap();
    let now = Instant::now();
    if let Ok(Some(lines)) = block_on(count_entry()) {
        println!("lines = {lines} cpu = {}", hipthread::sched_cpu_count().unwrap());
        println!("time: {:?}", now.elapsed());
    }
}

fn help() {
    println!("{} options [dir] ...", hictor::program_invocation_name());
    println!("options:");
    println!("--help | -h: print help message");
}

async fn count_entry() -> Option<usize> {
    let mut set = runtime::task::JoinSet::new();

    let opts = hiopt::options!["help", "h"];
    let args = unsafe { hiopt::raw_args_from_i8(hictor::args()) };

    for opt in opts.opt_iter(&args[1..]) {
        let (index, _) = opt.unwrap();
        match index {
            0..=1 => {
                help();
                return None;
            }
            _ => unreachable!(),
        }
    }

    for dir in opts.noopt_iter(&args[1..]) {
        let _ = set.spawn(visit_dir(dir)).await;
    }

    if set.is_empty() {
        let _ = set.spawn(visit_dir(".")).await;
    }

    let mut lines = 0;
    for (_, ret) in set.wait_all().await {
        lines += ret.unwrap();
    }
    Some(lines)
}

#[async_recursion::async_recursion]
async fn visit_dir<P: AsRef<Path> + Send + Debug>(dir: P) -> usize {
    let mut lines: usize = 0;
    let mut set = runtime::task::JoinSet::new();
    let Ok(dirs) = fs::read_dir(dir) else {
        return 0;
    };
    for entry in dirs {
        let path;
        match entry {
            Err(_) => continue,
            Ok(dir) => path = dir.path(),
        }
        if path.is_symlink() {
            continue;
        }
        if path.is_dir() {
            let _ = set.spawn(visit_dir(path)).await;
        } else if path.is_file() {
            let _ = set.spawn(count_lines(path)).await;
        }
    }

    for (_, ret) in set.wait_all().await {
        lines += ret.unwrap();
    }
    lines
}

async fn count_lines<P: AsRef<Path> + Send + Debug>(path: P) -> usize {
    let Ok(data) = fs::read(path) else {
        return 0;
    };
    let mut lines = if data.is_empty() { 0 } else { 1 };
    for b in data {
        if b == b'\n' {
            lines += 1;
        }
    }
    lines
}
